```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Spring循环依赖深度解析</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            color: #333;
            line-height: 1.8;
            background-color: #f8fafc;
        }
        .article-container {
            max-width: 960px;
            margin: 0 auto;
            padding: 2rem;
            background: white;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
            border-radius: 8px;
        }
        .hero {
            background: linear-gradient(135deg, #6e8efb 0%, #a777e3 100%);
            color: white;
            padding: 4rem 2rem;
            border-radius: 8px;
            margin-bottom: 3rem;
            text-align: center;
            position: relative;
            overflow: hidden;
        }
        .hero h1 {
            font-size: 2.8rem;
            font-weight: 700;
            margin-bottom: 1.5rem;
            font-family: 'Noto Serif SC', serif;
        }
        .hero p {
            font-size: 1.2rem;
            max-width: 800px;
            margin: 0 auto 2rem;
            opacity: 0.9;
        }
        .hero::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 100%;
            background: url('');
            opacity: 0.3;
            z-index: 0;
        }
        .hero-content {
            position: relative;
            z-index: 1;
        }
        h1, h2, h3, h4 {
            font-family: 'Noto Serif SC', serif;
            color: #1e293b;
            margin-top: 2rem;
            margin-bottom: 1rem;
            font-weight: 600;
        }
        h2 {
            font-size: 1.8rem;
            border-bottom: 2px solid #e2e8f0;
            padding-bottom: 0.5rem;
            margin-top: 3rem;
        }
        h3 {
            font-size: 1.4rem;
            color: #4f46e5;
        }
        p {
            margin-bottom: 1.5rem;
        }
        .code-block {
            background-color: #282c34;
            color: #abb2bf;
            padding: 1.5rem;
            border-radius: 6px;
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
            margin: 1.5rem 0;
            overflow-x: auto;
            font-size: 0.9rem;
            line-height: 1.6;
        }
        .important-note {
            background-color: #f0f9ff;
            border-left: 4px solid #3b82f6;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border-radius: 0 4px 4px 0;
        }
        .warning-note {
            background-color: #fff7ed;
            border-left: 4px solid #f59e0b;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border-radius: 0 4px 4px 0;
        }
        .error-note {
            background-color: #fef2f2;
            border-left: 4px solid #ef4444;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border-radius: 0 4px 4px 0;
        }
        .success-note {
            background-color: #f0fdf4;
            border-left: 4px solid #10b981;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border-radius: 0 4px 4px 0;
        }
        .diagram-container {
            background-color: white;
            padding: 1.5rem;
            border-radius: 6px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            margin: 2rem 0;
            text-align: center;
        }
        .diagram-container img {
            max-width: 100%;
            height: auto;
            border-radius: 4px;
        }
        .step-list {
            counter-reset: step-counter;
            padding-left: 0;
            margin: 2rem 0;
        }
        .step-list li {
            position: relative;
            padding-left: 3rem;
            margin-bottom: 1.5rem;
            list-style: none;
        }
        .step-list li::before {
            counter-increment: step-counter;
            content: counter(step-counter);
            position: absolute;
            left: 0;
            top: 0;
            width: 2rem;
            height: 2rem;
            background-color: #4f46e5;
            color: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
        }
        .footer {
            background-color: #1e293b;
            color: #e2e8f0;
            padding: 2rem 0;
            margin-top: 3rem;
            text-align: center;
        }
        .footer a {
            color: #93c5fd;
            text-decoration: none;
            transition: color 0.2s;
        }
        .footer a:hover {
            color: #60a5fa;
            text-decoration: underline;
        }
        .highlight {
            background-color: #fef3c7;
            padding: 0.2rem 0.4rem;
            border-radius: 4px;
            font-weight: 500;
        }
        .code-keyword {
            color: #c678dd;
        }
        .code-type {
            color: #e5c07b;
        }
        .code-string {
            color: #98c379;
        }
        .code-comment {
            color: #5c6370;
            font-style: italic;
        }
        .code-annotation {
            color: #56b6c2;
        }
        .code-property {
            color: #d19a66;
        }
        .code-value {
            color: #d19a66;
        }
        .card {
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            padding: 1.5rem;
            margin: 1.5rem 0;
            transition: transform 0.2s, box-shadow 0.2s;
        }
        .card:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
        }
        .cache-level {
            display: flex;
            justify-content: space-between;
            margin: 2rem 0;
            flex-wrap: wrap;
        }
        .cache-item {
            flex: 1;
            min-width: 250px;
            margin: 0.5rem;
            padding: 1.5rem;
            border-radius: 8px;
            background: white;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
            transition: transform 0.2s;
        }
        .cache-item:hover {
            transform: translateY(-5px);
        }
        .cache-item h4 {
            display: flex;
            align-items: center;
            color: #4f46e5;
        }
        .cache-item h4 i {
            margin-right: 0.5rem;
            font-size: 1.2rem;
        }
        .cache-item.primary {
            border-top: 4px solid #4f46e5;
        }
        .cache-item.secondary {
            border-top: 4px solid #10b981;
        }
        .cache-item.tertiary {
            border-top: 4px solid #f59e0b;
        }
        @media (max-width: 768px) {
            .hero h1 {
                font-size: 2rem;
            }
            .cache-level {
                flex-direction: column;
            }
            .cache-item {
                width: 100%;
                margin: 0.5rem 0;
            }
        }
    </style>
</head>
<body>
    <div class="hero">
        <div class="hero-content">
            <h1>Spring循环依赖深度解析</h1>
            <p>深入探讨Spring框架中Bean循环依赖的原理与解决方案</p>
            <div class="flex justify-center">
                <span class="inline-flex items-center px-3 py-1 rounded-full bg-white bg-opacity-20 text-white mr-2">
                    <i class="fas fa-code mr-1"></i> Spring
                </span>
                <span class="inline-flex items-center px-3 py-1 rounded-full bg-white bg-opacity-20 text-white">
                    <i class="fas fa-project-diagram mr-1"></i> 依赖注入
                </span>
            </div>
        </div>
    </div>

    <div class="article-container">
        <section>
            <h2><i class="fas fa-question-circle mr-2 text-purple-600"></i> 什么是循环依赖</h2>
            <p><strong>循环依赖</strong>就是N个类循环(嵌套)引用。通俗的讲就是N个Bean互相引用对方，最终形成<strong class="highlight">闭环</strong>。</p>
            
            <div class="diagram-container">
                <img src="https://cdn.nlark.com/yuque/0/2021/png/21449790/1631436094867-d7dc703e-0746-4d74-ae8e-7ebdd27cd6bb.png" alt="循环依赖示意图">
                <p class="text-sm text-gray-500 mt-2">循环依赖示意图 - A、B、C都代表对象，虚线代表引用关系</p>
            </div>
        </section>

        <section>
            <h2><i class="fas fa-leaf mr-2 text-green-600"></i> Spring Bean的循环依赖</h2>
            
            <h3><i class="fas fa-code mr-2 text-blue-500"></i> 1. field属性注入(setter方法注入)循环依赖</h3>
            <div class="code-block">
                <pre><code>
<span class="code-annotation">@Component</span>
<span class="code-keyword">public class</span> <span class="code-type">A</span> { <span class="code-comment">// 可以实例化A,创建B，再完成B的注入</span>
    <span class="code-annotation">@Autowired</span>
    <span class="code-keyword">private</span> <span class="code-type">B</span> b;
}

<span class="code-annotation">@Component</span>
<span class="code-keyword">public class</span> <span class="code-type">B</span> {
    <span class="code-annotation">@Autowired</span>
    <span class="code-keyword">private</span> <span class="code-type">A</span> a;
}
                </code></pre>
            </div>
            
            <div class="diagram-container">
                <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653470191354-8262bb05-35ca-4f8d-a096-f22cb5f9bb12.png" alt="运行结果1" class="mb-2">
                <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653470208226-d800bea3-cf23-4ca4-8e0a-9fe092f081da.png" alt="运行结果2">
                <p class="text-sm text-gray-500 mt-2">Spring对循环依赖的警告信息</p>
            </div>
            
            <div class="warning-note">
                <p>Spring不鼓励使用循环引用，默认情况下是禁止使用的，Spring建议是删除bean之间的循环依赖作为最后的手段。</p>
            </div>
            
            <div class="code-block">
                <pre><code>
<span class="code-comment"># 通过设置以下属性可以开启Spring解决循环依赖</span>
<span class="code-property">spring.main.allow-circular-references</span>=<span class="code-value">true</span>
                </code></pre>
            </div>
            
            <div class="error-note">
                <p><i class="fas fa-exclamation-triangle mr-2"></i> 设置以上属性后项目启动成功，能够正常work，注意一个SpringBoot版本问题，2.3.3演示不出来，需要使用2.7.0来演示。</p>
            </div>

            <h3><i class="fas fa-code mr-2 text-blue-500"></i> 2. 构造器注入循环依赖</h3>
            <div class="code-block">
                <pre><code>
<span class="code-annotation">@Service</span>
<span class="code-keyword">public class</span> <span class="code-type">A</span> { <span class="code-comment">// 这里A都没办法实例化(调用构造器)</span>
    <span class="code-keyword">private</span> <span class="code-type">B</span> b;
    
    <span class="code-keyword">public</span> <span class="code-type">A</span>(<span class="code-type">B</span> b) {
      <span class="code-keyword">this</span>.b = b;
    }
}

<span class="code-annotation">@Service</span>
<span class="code-keyword">public class</span> <span class="code-type">B</span> {
    <span class="code-keyword">private</span> <span class="code-type">A</span> a;
    
    <span class="code-keyword">public</span> <span class="code-type">B</span>(<span class="code-type">A</span> a) {
        <span class="code-keyword">this</span>.a = a;
    }
}
                </code></pre>
            </div>
            
            <div class="error-note">
                <p>构造器注入构成的循环依赖，此种循环依赖方式是<strong>无法解决</strong>的，只能抛出<strong class="highlight">BeanCurrentlyInCreationException</strong>异常表示循环依赖。这也是构造器注入的最大劣势（它有很多独特的优势）。</p>
            </div>
            
            <div class="important-note">
                <p><strong><i class="fas fa-lightbulb mr-2"></i> 根本原因：</strong>Spring解决循环依赖依靠的是Bean的"<strong>中间态</strong>"这个概念，而这个中间态指的是<strong class="highlight">已经实例化</strong>，但还没初始化的状态。而构造器是完成实例化的东西，所以构造器的循环依赖无法解决。</p>
            </div>
        </section>

        <section>
            <h2><i class="fas fa-layer-group mr-2 text-blue-600"></i> Spring容器的三级缓存</h2>
            <p>Spring中使用二级缓存解决了循环依赖的问题，第三级缓存是解决AOP的问题。</p>
            
            <div class="code-block">
                <pre><code>
<span class="code-keyword">public class</span> <span class="code-type">DefaultSingletonBeanRegistry</span> <span class="code-keyword">extends</span> <span class="code-type">SimpleAliasRegistry</span> <span class="code-keyword">implements</span> <span class="code-type">SingletonBeanRegistry</span> {
    <span class="code-comment">// 从上至下 分表代表这"三级缓存"</span>
    <span class="code-keyword">private final</span> <span class="code-type">Map</span>&lt;<span class="code-type">String</span>, <span class="code-type">Object</span>&gt; singletonObjects = <span class="code-keyword">new</span> <span class="code-type">ConcurrentHashMap</span>&lt;&gt;(<span class="code-value">256</span>); <span class="code-comment">//一级缓存</span>
    <span class="code-keyword">private final</span> <span class="code-type">Map</span>&lt;<span class="code-type">String</span>, <span class="code-type">Object</span>&gt; earlySingletonObjects = <span class="code-keyword">new</span> <span class="code-type">HashMap</span>&lt;&gt;(<span class="code-value">16</span>); <span class="code-comment">// 二级缓存</span>
    <span class="code-keyword">private final</span> <span class="code-type">Map</span>&lt;<span class="code-type">String</span>, <span class="code-type">ObjectFactory</span>&lt;?&gt;&gt; singletonFactories = <span class="code-keyword">new</span> <span class="code-type">HashMap</span>&lt;&gt;(<span class="code-value">16</span>); <span class="code-comment">// 三级缓存</span>
}
                </code></pre>
            </div>
        </section>

        <section>
            <h2><i class="fas fa-cogs mr-2 text-indigo-600"></i> Spring创建Bean的流程</h2>
            
            <div class="diagram-container">
                <img src="https://cdn.nlark.com/yuque/0/2021/png/21449790/1631437071746-730e96e7-32ae-4f24-a98b-48ce354b34e7.png" alt="Spring创建Bean的流程">
                <p class="text-sm text-gray-500 mt-2">Spring Bean创建流程图</p>
            </div>
            
            <p>对Bean的创建最为核心三个方法解释如下：</p>
            
            <ul class="list-disc pl-6 space-y-2">
                <li><strong class="highlight">createBeanInstance</strong>：实例化，其实也就是调用对象的<strong>构造方法</strong>实例化对象</li>
                <li><strong class="highlight">populateBean</strong>：填充属性，这一步主要是对bean的依赖属性进行注入(<strong class="highlight">@Autowired</strong>)</li>
                <li><strong class="highlight">initializeBean</strong>：回到一些形如<strong class="highlight">initMethod</strong>、<strong class="highlight">InitializingBean</strong>等方法</li>
            </ul>
            
            <div class="important-note">
                <p>从对<strong class="highlight">单例Bean</strong>的初始化可以看出，循环依赖主要发生在<strong>第二步（populateBean）</strong>，也就是field属性注入的处理。</p>
            </div>
        </section>

        <section>
            <h2><i class="fas fa-boxes mr-2 text-indigo-600"></i> 三级缓存详解</h2>
            
            <div class="cache-level">
                <div class="cache-item primary">
                    <h4><i class="fas fa-box-open"></i> 一级缓存</h4>
                    <p>Spring中的缓存底层都是一个Map，所有单例的bean初始化完成后会存放在一个Map(<strong>singletonObjects</strong>)中，beanName为key，单例bean为value。</p>
                    <ul class="list-disc pl-5 mt-2 text-sm">
                        <li>标记bean为创建中</li>
                        <li>new出bean对象</li>
                        <li>如果支持循环依赖则生成三级缓存，可以提前暴露bean</li>
                        <li>填充bean属性，解决属性依赖</li>
                        <li>初始化bean，处理Aware接口并执行各类bean后处理器，执行初始化方法</li>
                        <li>此时bean已经可以被使用，进行bean注册(标记)并注册销毁方法</li>
                        <li>将bean放入容器中(一级缓存)</li>
                    </ul>
                </div>
                
                <div class="cache-item secondary">
                    <h4><i class="fas fa-exchange-alt"></i> 二级缓存</h4>
                    <p>假如初始化A时，发现A 依赖B，即A实例化后执行到了填充属性，需要注入B，此时B还没有初始化，则需要暂停A，先去初始化B，那么此时new出来的A对象放哪里？</p>
                    <p class="mt-2">所以需要提供一个可以标记创建中的Map(二级缓存)，可以提前暴露正在创建的bean供其他bean依赖。</p>
                    <p class="mt-2">如果B也需要注入一个A的依赖(即发生循环依赖)，则B可以从创建中的map(二级缓存)中直接获取A对象（创建中）注入A，然后完成B的初始化，返回给正在注入属性的A，最终A也完成初始化。</p>
                </div>
                
                <div class="cache-item tertiary">
                    <h4><i class="fas fa-project-diagram"></i> 三级缓存</h4>
                    <p>当Bean没有循环依赖时，二级缓存是没有什么意义的。当有循环依赖但Bean并没有AOP代理，则会直接从二级缓存中原对象。</p>
                    <p class="mt-2"><strong>主要在当Bean存在循环依赖并且还有AOP代理时，三级缓存才有效果</strong>，三级缓存主要预防Bean有依赖时还可以完成代理增强。</p>
                    <p class="mt-2">本身Spring设计<strong>Bean的代理增强是在Bean初始化完成后</strong>AnnotationAwareAspectJ***Creator后置处理器中完成的。提前执行则和设计思路不符。</p>
                </div>
            </div>
            
            <div class="important-note">
                <p><strong><i class="fas fa-question-circle mr-2"></i> 为什么要设计三级缓存呢？</strong></p>
                <p>这是因为当我们需要使用AOP时，将会对原始对象进行代理，因此最后的对象将是代理对象而不是原始对象！</p>
            </div>
        </section>

        <section>
            <h2><i class="fas fa-clipboard-check mr-2 text-green-600"></i> 总结</h2>
            <p>依旧以上<strong class="highlight">A</strong>、<strong class="highlight">B</strong>类使用属性<strong class="highlight">field</strong>注入循环依赖的例子为例，对整个流程做文字步骤总结如下：</p>
            
            <ol class="step-list">
                <li>使用<strong class="highlight">context.getBean(A.class)</strong>，旨在获取容器内的单例A(若A不存在，就会走A这个Bean的创建流程)，显然初次获取A是不存在的，因此走<strong>A的创建之路~</strong></li>
                <li><strong class="highlight">实例化</strong>A（注意此处仅仅是实例化），并将它放进<strong class="highlight">缓存</strong>（此时A已经实例化完成，已经可以被引用了）</li>
                <li><strong class="highlight">属性注入</strong>A：<strong class="highlight">@Autowired</strong>依赖注入B（此时需要去容器内获取B）</li>
                <li>为了完成依赖注入B，会通过<strong class="highlight">getBean(B)</strong>去容器内找B。但此时B在容器内不存在，就走向<strong>B的创建之路~</strong></li>
                <li><strong class="highlight">实例化</strong>B，并将其放入缓存。（此时B也能够被引用了）</li>
                <li><strong class="highlight">属性注入</strong>，<strong class="highlight">@Autowired</strong>依赖注入A（此时需要去容器内获取A）</li>
                <li><strong class="highlight">此处重要</strong>：初始化B时会调用<strong class="highlight">getBean(A)</strong>去容器内找到A，上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里，所以这个时候去看缓存里是已经存在A的引用了的，所以<strong class="highlight">getBean(A)</strong>能够正常返回</li>
                <li><strong>B初始化成功</strong>（此时已经注入A成功了，已成功持有A的引用了），return（注意此处return相当于是返回最上面的<strong class="highlight">getBean(B)</strong>这句代码，回到了初始化A的流程中~）。</li>
                <li>因为B实例已经成功返回了，因此最终<strong>A也初始化成功</strong></li>
                <li><strong>到此，B持有的已经是初始化完成的A，A持有的也是初始化完成的B，完美~</strong></li>
            </ol>
            
            <div class="card">
                <h3><i class="fas fa-key mr-2"></i> 关键要点总结</h3>
                <ul class="list-disc pl-6 space-y-2">
                    <li><strong>没有循环依赖情况下</strong>，只有一级缓存生效，二三级缓存用不到</li>
                    <li><strong>二级缓存</strong>：存储提前暴露的bean，真正的解决循环依赖是靠二级缓存的，earlySingletonObjects</li>
                    <li><strong>三级缓存</strong>：存储bean和其要加强的aop处理，如果需要aop增强的bean遇到了循环依赖，则使用该缓存中的aop处理代理增强bean</li>
                </ul>
            </div>
        </section>
    </div>

    <footer class="footer">
        <div class="container mx-auto px-4">
            <p class="text-sm">技术小馆</p>
            <p class="text-sm mt-1">
                <a href="http://www.yuque.com/jtostring" target="_blank">http://www.yuque.com/jtostring</a>
            </p>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```